/*
* This file is part of the Weborganic XMLDoclet library.
*
* For licensing information please see the file license.txt included in the release.
* A copy of this licence can also be found at
* http://www.opensource.org/licenses/artistic-license-2.0.php
*/
package org.weborganic.xmldoclet;
import java.io.File;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.sun.javadoc.AnnotationDesc;
import com.sun.javadoc.AnnotationDesc.ElementValuePair;
import com.sun.javadoc.AnnotationTypeElementDoc;
import com.sun.javadoc.AnnotationValue;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.ConstructorDoc;
import com.sun.javadoc.Doc;
import com.sun.javadoc.DocErrorReporter;
import com.sun.javadoc.ExecutableMemberDoc;
import com.sun.javadoc.FieldDoc;
import com.sun.javadoc.LanguageVersion;
import com.sun.javadoc.MethodDoc;
import com.sun.javadoc.PackageDoc;
import com.sun.javadoc.ParamTag;
import com.sun.javadoc.Parameter;
import com.sun.javadoc.ProgramElementDoc;
import com.sun.javadoc.RootDoc;
import com.sun.javadoc.SeeTag;
import com.sun.javadoc.Tag;
import com.sun.javadoc.Type;
import com.sun.javadoc.ThrowsTag;
import com.sun.javadoc.TypeVariable;
import com.sun.tools.doclets.Taglet;
/**
* The Doclet implementation to use with javadoc.
*
* <p>A Doclet to be used with JavaDoc which will output XML with all of the information from the JavaDoc.
*
* @author Christophe Lauret
* @author Johan Johansson
* @version 24 May 2013
*/
public final class XMLDoclet {
public final static String VERSION = "1.3.1";
/**
* The date format matching ISO 8601, easier to parse with XSLT.
*/
private static final String ISO_8601 = "yyyy-MM-dd'T'HH:mm:ss";
// Methods required by doclet specifications ====================================================
/**
* The method used by this method invocation.
*/
private static Options options = null;
public static RootDoc root;
/**
* Processes the JavaDoc documentation.
*
* <p>This method is required for all doclets.
*
* @see com.sun.javadoc.Doclet#start(RootDoc)
*
* @param root The root of the documentation tree.
*
* @return <code>true</code> if processing was successful.
*/
public static boolean start(RootDoc root) {
XMLDoclet.root = root;
// Create the root node.
List<XMLNode> nodes = toXMLNodes(root);
// Save the output XML
save(nodes);
return true;
}
/**
* Returns the version of the Java Programming Language supported by this Doclet.
*
* <p>This Doclet supports Java 5.
*
* @see com.sun.javadoc.Doclet#languageVersion()
*
* @return {@value LanguageVersion#JAVA_1_5}
*/
public static LanguageVersion languageVersion() {
return LanguageVersion.JAVA_1_5;
}
/**
* Returns the number of arguments required for the given option.
*
* <p>This method calls {@link Options#getLength(String)}.
*
* @see com.sun.javadoc.Doclet#optionLength(String)
* @see Options#getLength(String)
*
* @param option The name of the option.
*
* @return The number of arguments for that option.
*/
public static int optionLength(String option) {
return Options.getLength(option);
}
/**
* Check that options have the correct arguments.
*
* <p>This method is not required, but is recommended, as every option will be considered valid
* if this method is not present. It will default gracefully (to true) if absent.
*
* <p>Printing option related error messages (using the provided DocErrorReporter) is the
* responsibility of this method.
*
* @see com.sun.javadoc.Doclet#validOptions(String[][], DocErrorReporter)
*
* @param options The two dimensional array of options.
* @param reporter The error reporter.
*
* @return <code>true</code> if the options are valid.
*/
public static boolean validOptions(String o[][], DocErrorReporter reporter) {
options = Options.toOptions(o, reporter);
// OK if we could set up the options.
return options != null;
}
// Methods specific to this doclet implementation ===============================================
/**
* Save the given array of nodes.
*
* Will either save the files individually, or as a single file depending on the existence of the "-multiple" flag.
*
* @param nodes The array of nodes to be saved.
*/
private static void save(List<XMLNode> nodes) {
// Add admin node
XMLNode meta = new XMLNode("meta");
DateFormat df = new SimpleDateFormat(ISO_8601);
meta.attribute("created", df.format(new Date()));
XMLNode meta2 = new XMLNode("meta");
meta2.attribute("generator", VERSION);
// Multiple files
if (options.useMultipleFiles()) {
for (XMLNode node : nodes) {
File dir = options.getDirectory();
String name = node.getAttribute("name");
if (options.useSubFolders()) {
name = name.replace('.', '/');
int x = name.lastIndexOf('/');
if (x >= 0) {
dir = new File(dir, name.substring(0,x));
dir.mkdirs();
name = name.substring(x+1);
}
}
XMLNode root = new XMLNode("root");
root.attribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
root.child(meta);
root.child(meta2);
root.child(node);
String fileName = name + ".xml";
root.save(dir, fileName, options.getEncoding(), "");
}
// Index
XMLNode root = new XMLNode("root");
root.attribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
root.child(meta);
root.child(meta2);
for (XMLNode node : nodes) {
String name = node.getAttribute("name");
if (options.useSubFolders()) name = name.replace('.', '/');
XMLNode ref = new XMLNode(node.getName());
ref.attribute("xlink:type", "simple");
ref.attribute("xlink:href", name + ".xml");
root.child(ref);
}
String fileName = "index.xml";
root.save(options.getDirectory(), fileName, options.getEncoding(), "");
// Single file
} else {
// Wrap the XML
XMLNode root = new XMLNode("root");
root.attribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
root.child(meta);
root.child(meta2);
for (XMLNode node : nodes) {
root.child(node);
}
root.save(options.getDirectory(), options.getFilename(), options.getEncoding(), "");
}
}
/**
* Returns the XML nodes for all the selected classes in the specified RootDoc.
*
* @param root The RootDoc from which the XML should be built.
* @return The list of XML nodes which represents the RootDoc.
*/
private static List<XMLNode> toXMLNodes(RootDoc root) {
List<XMLNode> nodes = new ArrayList<XMLNode>();
// Iterate over the classes
for (ClassDoc doc : root.classes()) {
if (options.filter(doc)) {
nodes.add(toClassNode(doc));
}
}
// Iterate over packages
if (!options.hasFilter()) {
if (root.specifiedPackages().length > 0) {
for (PackageDoc doc : root.specifiedPackages()) {
nodes.add(toPackageNode(doc));
}
} else {
// no packages specified, iterate over classes and fetch the package that way
List<PackageDoc> packages = new ArrayList<PackageDoc>();
for (ClassDoc doc : root.classes()) {
PackageDoc pkg = doc.containingPackage();
if (pkg != null && !packages.contains(pkg)) {
packages.add(pkg);
nodes.add(toPackageNode(pkg));
}
}
}
}
return nodes;
}
/**
* Returns the XML node corresponding to the specified ClassDoc.
*
* @param doc The package to transform.
*/
private static XMLNode toPackageNode(PackageDoc doc) {
XMLNode node = new XMLNode("package", doc);
// Core attributes
node.attribute("name", doc.name());
// Comments
node.child(toShortComment(doc));
node.child(toComment(doc));
// Child nodes
node.child(toAnnotationsNode(doc.annotations()));
node.child(toStandardTags(doc));
node.child(toTags(doc));
node.child(toSeeNodes(doc.seeTags()));
return node;
}
/**
* Returns the XML node corresponding to the specified ClassDoc.
*
* @param classDoc The class to transform.
*/
private static XMLNode toClassNode(ClassDoc classDoc) {
XMLNode node = new XMLNode("class", classDoc);
// Core attributes
node.attribute("type", classDoc.name());
node.attribute("fulltype", classDoc.qualifiedName());
node.attribute("name", classDoc.qualifiedName());
node.attribute("package", classDoc.containingPackage().name());
node.attribute("visibility", getVisibility(classDoc));
// generic type parameters
node.child(toTypeParametersNode(classDoc));
// Interfaces
ClassDoc[] interfaces = classDoc.interfaces();
if (interfaces.length > 0) {
XMLNode implement = new XMLNode("implements");
for (ClassDoc i : interfaces) {
XMLNode interfce = new XMLNode("interface");
interfce.attribute("type", i.name());
interfce.attribute("fulltype", i.qualifiedName());
implement.child(interfce);
}
node.child(implement);
}
// Superclass
if (classDoc.superclass() != null) {
node.attribute("superclass", classDoc.superclass().name());
node.attribute("superclassfulltype", classDoc.superclass().qualifiedName());
}
// Class properties
node.attribute("ordinaryClass", classDoc.isOrdinaryClass());
node.attribute("interface", classDoc.isInterface());
node.attribute("final", classDoc.isFinal());
node.attribute("static", classDoc.isStatic());
node.attribute("abstract", classDoc.isAbstract());
node.attribute("serializable", classDoc.isSerializable());
node.attribute("enum", classDoc.isEnum());
node.attribute("error", classDoc.isError());
node.attribute("exception", classDoc.isException());
// Comments
node.child(toShortComment(classDoc));
node.child(toComment(classDoc));
// Deprecated
toDeprecated(classDoc, node);
// Inheritance tree
XMLNode inheritance = new XMLNode("inheritance");
ClassDoc superClass = classDoc.superclass();
if (superClass != null) {
while (superClass != null) {
XMLNode tmp = new XMLNode("class");
tmp.attribute("type", superClass.name());
tmp.attribute("fulltype", superClass.qualifiedName());
inheritance.child(tmp);
superClass = superClass.superclass();
}
node.child(inheritance);
}
// Other child nodes
node.child(toAnnotationsNode(classDoc.annotations()));
node.child(toStandardTags(classDoc));
node.child(toTags(classDoc));
node.child(toSeeNodes(classDoc.seeTags()));
node.child(toFieldsNode(classDoc.fields()));
node.child(toEnumConstantsNode(classDoc.enumConstants()));
node.child(toConstructorsNode(classDoc.constructors()));
node.child(toMethods(classDoc.methods()));
// Handle inner classes
for (ClassDoc inner : classDoc.innerClasses()) {
node.child(toClassNode(inner));
}
return node;
}
/**
* Generates a child node for each generic type parameter.
* @param doc [description]
* @return [description]
*/
private static XMLNode toTypeParametersNode(ClassDoc doc) {
TypeVariable[] typeParameters = doc.typeParameters();
ParamTag[] typeParameterTags = doc.typeParamTags();
XMLNode node = null;
if (typeParameters != null && typeParameters.length > 0) {
node = new XMLNode("typeParameters");
for (TypeVariable i : typeParameters) {
XMLNode paramNode = new XMLNode("parameter");
paramNode.attribute("name", i.typeName());
Type[] bounds = i.bounds();
if (bounds != null && bounds.length > 0) {
XMLNode boundsNode = new XMLNode("bounds");
for (Type t : bounds) {
XMLNode typeNode = new XMLNode("type");
typeNode.attribute("fulltype", t.toString());
typeNode.attribute("type", t.typeName());
typeNode.attribute("name", t.qualifiedTypeName());
boundsNode.child(typeNode);
}
paramNode.child(boundsNode);
}
if (typeParameterTags != null && typeParameterTags.length > 0) {
ParamTag tag = null;
for (ParamTag j : typeParameterTags) {
// take the first one
if (j.isTypeParameter() && j.parameterName().equals(i.typeName())) {
tag = j;
break;
}
}
if (tag != null) {
XMLNode commentNode = new XMLNode("comment");
commentNode.text(toComment(tag));
paramNode.child(commentNode);
}
}
node.child(paramNode);
}
}
return node;
}
/**
* Returns the specified field as an XML node.
*
* @param field A field.
* @return The corresponding node.
*/
private static XMLNode toFieldNode(FieldDoc field) {
// Create the <field> node and populate it.
XMLNode node = new XMLNode("field");
node.attribute("name", field.name());
node.attribute("type", field.type().typeName());
node.attribute("fulltype", field.type().toString());
node.attribute("dimension", field.type().dimension());
if (field.constantValue() != null && field.constantValue().toString().length() > 0)
node.attribute("const", field.constantValue().toString());
if (field.constantValueExpression() != null && field.constantValueExpression().length() > 0)
node.attribute("constexpr", field.constantValueExpression());
node.attribute("static", field.isStatic());
node.attribute("final", field.isFinal());
node.attribute("transient", field.isTransient());
node.attribute("volatile", field.isVolatile());
node.attribute("visibility", getVisibility(field));
// Comment
node.child(toShortComment(field));
node.child(toComment(field));
// Deprecated
toDeprecated(field, node);
// Other child nodes
node.child(toStandardTags(field));
node.child(toTags(field));
node.child(toSeeNodes(field.seeTags()));
return node;
}
/**
* Returns the .
*
* @param constructors The constructors.
* @param node The node to add the XML to.
*/
private static XMLNode toConstructorsNode(ConstructorDoc[] constructors) {
if (constructors.length < 1) return null;
// Create the <constructors> node
XMLNode node = new XMLNode("constructors");
// Add the <constructor> nodes
for (ConstructorDoc constructor : constructors) {
XMLNode c = new XMLNode("constructor");
updateExecutableMemberNode(constructor, c);
// standard block tags
c.child(toStandardTags(constructor));
// deprecated
toDeprecated(constructor, c);
node.child(c);
}
return node;
}
/**
* Transforms an array of methods and an array of constructor methods into XML and adds those to the host node.
*
* @param methods The methods.
* @param constructors The constructors.
* @param node The node to add the XML to.
*/
private static XMLNode toMethods(MethodDoc[] methods) {
if (methods.length < 1) return null;
// Create the <methods> node
XMLNode node = new XMLNode("methods");
// Add the <method> nodes
for (MethodDoc method : methods) {
XMLNode methodNode = new XMLNode("method");
updateExecutableMemberNode(method, methodNode);
methodNode.attribute("type", method.returnType().typeName());
methodNode.attribute("fulltype", method.returnType().toString());
methodNode.attribute("dimension", method.returnType().dimension());
methodNode.attribute("abstract", method.isAbstract());
methodNode.attribute("varargs", method.isVarArgs());
MethodDoc overrides = method.overriddenMethod();
if (overrides != null) {
methodNode.attribute("overrides", overrides.toString());
}
Tag[] returnTags = method.tags("@return");
if (returnTags.length > 0) {
XMLNode returnNode = new XMLNode("returns");
returnNode.text(toComment(returnTags[0]));
methodNode.child(returnNode);
}
// standard block tags
methodNode.child(toStandardTags(method));
// Deprecated
toDeprecated(method, methodNode);
node.child(methodNode);
}
return node;
}
/**
* Returns the fields node.
*
* @param fields The set of fields.
* @return the fields or <code>null</code> if none.
*/
private static XMLNode toFieldsNode(FieldDoc[] fields) {
if (fields.length < 1) return null;
// Iterate over the fields
XMLNode node = new XMLNode("fields");
for (FieldDoc field : fields) {
node.child(toFieldNode(field));
}
return node;
}
/**
* Returns the enumConstants node.
*
* @param fields The set of fields.
* @return the fields or <code>null</code> if none.
*/
private static XMLNode toEnumConstantsNode(FieldDoc[] fields) {
if (fields.length < 1) return null;
// Iterate over the fields
XMLNode node = new XMLNode("enumConstants");
for (FieldDoc field : fields) {
node.child(toFieldNode(field));
}
return node;
}
/**
* Set the commons attribute and child nodes for method and constructor nodes.
*
* @param member The executable member documentation.
* @param node The node to update
*/
private static void updateExecutableMemberNode(ExecutableMemberDoc member, XMLNode node) {
// Add the basic attribute values
node.attribute("name", member.name());
node.attribute("static", member.isStatic());
node.attribute("interface", member.isInterface());
node.attribute("final", member.isFinal());
node.attribute("visibility", getVisibility(member));
node.attribute("synchronized", member.isSynchronized());
node.attribute("synthetic", member.isSynthetic());
// Comments
node.child(toShortComment(member));
node.child(toComment(member));
// Other objects attached to the method/constructor.
node.child(toTags(member));
node.child(toSeeNodes(member.seeTags()));
node.child(toParametersNode(member.parameters(), member.paramTags(), member.isVarArgs()));
node.child(toExceptionsNode(member.thrownExceptionTypes(), member.throwsTags()));
}
/**
* Transforms common tags on the Doc object into XML.
*
* @param doc The Doc object.
* @return The corresponding list of nodes.
*/
private static List<XMLNode> toStandardTags(Doc doc) {
// Create the comment node
List<XMLNode> nodes = new ArrayList<XMLNode>();
// Handle the tags
for (Tag tag : doc.tags()) {
Taglet taglet = options.getTagletForName(tag.name().length() > 1? tag.name().substring(1) : "");
if (taglet instanceof BlockTag) {
nodes.add(((BlockTag) taglet).toXMLNode(tag));
}
}
// Add the node to the host
return nodes;
}
/**
* Transforms comments on the Doc object into XML.
*
* @param doc The Doc object.
* @param node The node to add the comment nodes to.
*/
private static XMLNode toTags(Doc doc) {
// Create the comment node
XMLNode node = new XMLNode("tags");
boolean hasTags = false;
// Handle the tags
for (Tag tag : doc.tags()) {
Taglet taglet = options.getTagletForName(tag.name().length() > 1? tag.name().substring(1) : "");
if (taglet != null && !(taglet instanceof BlockTag)) {
XMLNode tNode = new XMLNode("tag");
tNode.attribute("name", tag.name());
tNode.text(taglet.toString(tag));
node.child(tNode);
hasTags = true;
}
}
// Add the node to the host
return hasTags? node : null;
}
// Aggregate XML methods ========================================================================
/**
* Returns the XML for the specified parameters using the param tags for additional description.
*
* @param parameters parameters instances to process
* @param tags corresponding parameter tags (not necessarily in the same order)
*
* @return the XML for the specified parameters using the param tags for additional description.
*/
private static XMLNode toParametersNode(Parameter[] parameters, ParamTag[] tags, boolean isVarArgs) {
if (parameters.length == 0) return null;
// Iterate over the parameters
XMLNode node = new XMLNode("parameters");
int pos = 0;
for (Parameter parameter : parameters) {
XMLNode p = toParameterNode(parameter, find(tags, parameter.name()), isVarArgs && pos++ == (parameters.length - 1));
node.child(p);
}
return node;
}
/**
* Returns the XML for the specified exceptions using the throws tags for additional description.
*
* @param exceptions exceptions instances to process
* @param tags corresponding throws tags (not necessarily in the same order)
*
* @return the XML for the specified parameters using the param tags for additional description.
*/
private static XMLNode toExceptionsNode(Type[] exceptions, ThrowsTag[] tags) {
if (exceptions.length == 0) return null;
// Iterate over the exceptions
XMLNode node = new XMLNode("exceptions");
for (Type exception : exceptions) {
XMLNode n = toExceptionNode(exception, find(tags, exception.typeName()));
node.child(n);
}
return node;
}
/**
* Transforms comments on the Doc object into XML.
*
* @param doc The Doc object.
* @param node The node to add the comment nodes to.
*/
private static List<XMLNode> toSeeNodes(SeeTag[] tags) {
if (tags == null || tags.length == 0) return Collections.emptyList();
List<XMLNode> nodes = new ArrayList<XMLNode>(tags.length);
for (SeeTag tag : tags) {
XMLNode n = toSeeNode(tag);
if (n != null) nodes.add(n);
}
// Add the node to the host
return nodes;
}
/**
* Returns the XML node corresponding to the specified ClassDoc.
*
* @param classDoc The class to transform.
*/
private static XMLNode toAnnotationsNode(AnnotationDesc[] annotations) {
if (annotations.length < 1) return null;
XMLNode node = new XMLNode("annotations");
for (AnnotationDesc annotation : annotations) {
node.child(toAnnotationNode(annotation));
}
return node;
}
// Atomic XML methods ===========================================================================
/**
* Returns the XML for a see tag.
*
* @param tag The See tag to process.
*/
private static XMLNode toSeeNode(SeeTag tag) {
if (tag == null) return null;
XMLNode see = new XMLNode("see");
boolean multiple = options.useMultipleFiles();
// A link
if (tag.text().startsWith("<a")) {
String text = tag.text();
Matcher href = Pattern.compile("href=\"(.+)\"").matcher(text);
if (href.find()) {
see.attribute("type", "custom");
see.attribute("href", href.group(1));
}
Matcher title = Pattern.compile("\\>(.+)\\<\\/").matcher(text);
if (title.find()) {
see.attribute("title", title.group(1));
}
} else {
// A referenced Package, class and/or member
String pkg = null;
String cls = null;
String mbr = null;
String type = tag.referencedMember() != null ? "member" : tag.referencedClass() != null ? "class" : tag.referencedPackage() != null ? "package" : "unknown";
if (tag.referencedPackage() != null) {
pkg = tag.referencedPackage().name();
} else if (tag.referencedClass() != null && tag.referencedClass().containingPackage() != null) {
pkg = tag.referencedClass().containingPackage().name();
}
cls = tag.referencedClass() != null ? tag.referencedClass().name() : null;
mbr = tag.referencedMemberName();
see.attribute("type", type);
if (type == "unknown") {
see.attribute("text", tag.text());
} else {
see.attribute("package", pkg);
if (cls != null && cls.length() > 0) {
see.attribute("class", cls);
}
String label = tag.label();
if (tag.referencedMember() != null && tag.referencedMember() instanceof ExecutableMemberDoc) {
ExecutableMemberDoc memberDoc = (ExecutableMemberDoc)tag.referencedMember();
mbr = memberDoc.name() + memberDoc.signature();
if (label == null || label.length() == 0) {
label = memberDoc.name() + memberDoc.flatSignature();
}
}
if (mbr != null && mbr.length() > 0) {
see.attribute("member", mbr);
}
see.attribute("title", label);
}
}
return see;
}
/**
* Returns the XML for a parameter and its corresponding param tag.
*
* @return The corresponding XML.
*/
private static XMLNode toParameterNode(Parameter parameter, ParamTag tag, boolean isVarArgs) {
if (parameter == null) return null;
XMLNode node = new XMLNode("parameter");
node.attribute("name", parameter.name());
node.attribute("type", parameter.type().typeName());
node.attribute("fulltype", parameter.type().toString());
String dimension = parameter.type().dimension();
if (isVarArgs) {
dimension = dimension.replaceAll("\\[\\]$", "") + "...";
}
node.attribute("dimension", dimension);
node.attribute("varargs", isVarArgs);
if (tag!= null) {
node.text(toComment(tag));
}
return node;
}
/**
* Returns the XML for an exception and its corresponding throws tag.
*
* @return The corresponding XML.
*/
private static XMLNode toExceptionNode(Type exception, ThrowsTag tag) {
if (exception == null) return null;
XMLNode node = new XMLNode("exception");
node.attribute("type", exception.typeName());
node.attribute("fulltype", exception.qualifiedTypeName());
if (tag != null) {
node.attribute("comment", tag.exceptionComment());
node.text(toComment(tag));
}
return node;
}
/**
* Transforms comments on the Doc object into XML.
*
* @param doc The Doc object.
* @param node The node to add the comment nodes to.
*/
private static XMLNode toComment(Doc doc) {
if (doc.commentText() == null || doc.commentText().length() == 0) return null;
XMLNode node = new XMLNode("comment", doc, doc.position().line());
StringBuilder comment = new StringBuilder();
// Analyse each token and produce comment node
for (Tag t : doc.inlineTags()) {
Taglet taglet = options.getTagletForName(t.name());
if (taglet != null) comment.append(taglet.toString(t));
else comment.append(t.text());
}
return node.text(comment.toString());
}
/**
* Transforms short comments on the Doc object into XML.
*
* @param doc The Doc object.
* @param node The node to add the comment nodes to.
*/
private static XMLNode toShortComment(Doc doc) {
if (doc.commentText() == null || doc.commentText().length() == 0) return null;
XMLNode node = new XMLNode("shortComment", doc, doc.position().line());
StringBuilder comment = new StringBuilder();
// Analyse each token and produce comment node
for (Tag t : doc.firstSentenceTags()) {
Taglet taglet = options.getTagletForName(t.name());
if (taglet != null) comment.append(taglet.toString(t));
else comment.append(t.text());
}
return node.text(comment.toString());
}
/**
* Transforms comments on the Doc object into XML.
*
* @param doc The Doc object.
* @param node The node to add the comment nodes to.
*/
private static String toComment(Tag tag) {
if (tag.text() == null || tag.text().length() == 0) return null;
StringBuilder comment = new StringBuilder();
// Analyse each token and produce comment node
for (Tag t : tag.inlineTags()) {
Taglet taglet = options.getTagletForName(t.name());
if (taglet != null) comment.append(taglet.toString(t));
else comment.append(t.text());
}
return comment.toString();
}
/**
* Transforms deprecated tags on the Doc object into XML.
*
* @param doc The Doc object.
* @param node The node to add the deprecated nodes to.
*/
private static XMLNode toDeprecated(Doc doc, XMLNode node) {
Tag[] deprecatedTags = doc.tags("@deprecated");
if (deprecatedTags.length > 0) {
StringBuilder shortText = new StringBuilder();
// Analyse each token and produce text node
for (Tag t : deprecatedTags[0].firstSentenceTags()) {
Taglet taglet = options.getTagletForName(t.name());
if (taglet != null) shortText.append(taglet.toString(t));
else shortText.append(t.text());
}
if (shortText.length() == 0) {
shortText.append("Deprecated.");
}
String fullText = toComment(deprecatedTags[0]);
fullText = fullText != null ? fullText.substring(shortText.length()).trim() : "";
XMLNode shortNode = new XMLNode("shortDeprecated", doc, doc.position().line());
XMLNode fullNode = new XMLNode("deprecated", doc, doc.position().line());
if (shortText.toString().length() > 0) {
shortNode.text(shortText.toString());
node.child(shortNode);
}
if (fullText.length() > 0) {
fullNode.text(fullText);
node.child(fullNode);
}
}
return null;
}
/**
*
* @return an "annotation" XML node for the annotation.
*/
private static XMLNode toAnnotationNode(AnnotationDesc annotation) {
if (annotation == null) return null;
XMLNode node = new XMLNode("annotation");
node.attribute("name", annotation.annotationType().name());
for (ElementValuePair pair : annotation.elementValues()) {
node.child(toPairNode(pair));
}
return node;
}
/**
*
* @return an "element" XML node for the element value pair.
*/
private static XMLNode toPairNode(ElementValuePair pair) {
if (pair == null) return null;
XMLNode node = new XMLNode("element");
AnnotationTypeElementDoc element = pair.element();
node.attribute("name", element.name());
node.child(toShortComment(element));
node.child(toComment(element));
node.child(toAnnotationValueNode(pair.value()));
return node;
}
/**
*
* @return an "value" or "array" XML node for the annotation value.
*/
private static XMLNode toAnnotationValueNode(AnnotationValue value) {
if (value == null) return null;
XMLNode node = null;
Object o = value.value();
Class<?> c = o.getClass();
if (c.isArray()) {
node = new XMLNode("array");
Object[]a = (Object[])o;
for (Object i : a) {
if (i instanceof AnnotationValue) {
node.child(toAnnotationValueNode((AnnotationValue)i));
} else {
System.err.println("Unexpected annotation value type"+i);
}
}
} else {
node = new XMLNode("value");
node.attribute("type", getAnnotationValueType(o));
node.attribute("value", o.toString());
}
return node;
}
// Utilities ====================================================================================
/**
* Sets the visibility for the class, method or field.
*
* @param member The member for which the visibility needs to be set (class, method, or field).
* @param node The node to which the visibility should be set.
*/
private static String getVisibility(ProgramElementDoc member) {
if (member.isPrivate()) return "private";
if (member.isProtected()) return "protected";
if (member.isPublic()) return "public";
if (member.isPackagePrivate()) return "package-private";
// Should never happen
return null;
}
/**
* Find the corresponding throws tag
*
* @return
*/
private static ThrowsTag find(ThrowsTag[] tags, String name){
for (ThrowsTag tag : tags) {
if (tag.exceptionName().equalsIgnoreCase(name)) {
return tag;
}
}
return null;
}
/**
* Find the corresponding parameter tag.
*
* @return
*/
private static ParamTag find(ParamTag[] tags, String name){
for (ParamTag tag : tags) {
if (tag.parameterName().equalsIgnoreCase(name)) {
return tag;
}
}
return null;
}
/**
* Returns the value type of the annotation depending on the specified object's class.
*
* @param o the object representing the type of annotation value.
* @return the primitive if any of full class name.
*/
private static String getAnnotationValueType(Object o) {
if (o instanceof String) return "String";
if (o instanceof Integer) return "int";
if (o instanceof Boolean) return "boolean";
if (o instanceof Long) return "long";
if (o instanceof Short) return "short";
if (o instanceof Float) return "float";
if (o instanceof Double) return "double";
if (o instanceof FieldDoc) {
return ((FieldDoc)o).containingClass().qualifiedName();
}
return o.getClass().getName();
}
}